use crate::util::{self, extract_resource_name};

use crate::ui::cui::ProgressBar;
use crate::util::{History, ProgressMonitor};
use std::{fs, path};
use std::io::ErrorKind;
use std::{
    collections::HashMap,
    fs::File,
    io::{BufRead, BufReader, Error},
};

pub struct M3u8Listener {
    path: String,
    progress_bar: ProgressBar,
    pub index_path: String,
    pub index_redirected_path: String,
    base_url: String,
    base_path_url: String,
    pub chunk_list: Vec<String>,
    pub key_url: String,
    pub encrypted_method: String,
    download_file_map: HashMap<String, String>,
}

impl M3u8Listener {
    pub fn new(path: &str, history: &History) -> Self {
        let mut download_map = HashMap::new();
        for chunk_file in history.iter() {
            let filename = util::extract_resource_name(chunk_file.as_str());
            download_map.insert(filename.to_string(), chunk_file.to_string());
        }

        Self {
            path: String::from(path),
            progress_bar: ProgressBar::new(),
            index_path: String::new(),
            index_redirected_path: String::new(),
            base_url: String::new(),
            base_path_url: String::new(),
            key_url: String::new(),
            encrypted_method: String::new(),
            chunk_list: Vec::new(),
            download_file_map: download_map,
        }
    }
}

impl ProgressMonitor for M3u8Listener {
    fn on_start(&mut self, url: &str, source_name: &str, destinaion: &str) {
        println!("{} -> {}", source_name, destinaion);
        self.index_path = String::from(destinaion);
        self.index_redirected_path = String::from(destinaion.replace("m3u8", "redirected.m3u8"));
        self.base_url = String::from(util::extract_base_url(url));
        self.base_path_url = String::from(util::extract_base_path_url(url));
        self.progress_bar.set_title(source_name);
    }

    fn on_downloaded(&mut self, source_name: &str, url: &str) -> () {
        self.download_file_map
            .insert(source_name.to_string(), url.to_string());
    }

    fn is_downloaded(&self, source_name: &str) -> bool {
        self.download_file_map.contains_key(source_name)
    }

    fn on_update(&mut self, downloaded: u64, total: u64) {
        self.progress_bar
            .set_progress((downloaded as f64 / total as f64 * 100.0) as f32);
        let extra = format!("({}/{})", downloaded, total);
        self.progress_bar.display_with_extra(extra.as_str());
    }

    fn on_end(&mut self, source_name: &str, downloaded: u64, total: u64) -> std::io::Result<()> {
        println!("");
        println!("{} downloaded.", source_name);

        let file = File::open(self.index_path.as_str()).unwrap();
        let reader = BufReader::new(file);

        let mut found = false;
        let mut redirected_m3u8_index = String::new();
        for (index, line) in reader.lines().enumerate() {
            let line = line.unwrap();
            let mut redirected_line = String::new();
            if index == 0 && line != "#EXTM3U" {
                return Err(Error::new(
                    ErrorKind::Unsupported,
                    "file format is not support.",
                ));
            } else if line.starts_with("#EXT-X-KEY:") {
                println!("encrypted detected.");
                if let Some(pos) = line.find(":") {
                    if let Some(fields_text) = line.get(pos + 1..) {
                        let field_list = fields_text.split(",");
                        for field in field_list {
                            if let Some(epos) = field.find('=') {
                                let field_name = field.get(0..epos).unwrap();
                                let field_value = field.get(epos + 1..).unwrap();
                                if field_name.to_lowercase() == "method" {
                                    self.encrypted_method =
                                        String::from(field_value.to_lowercase());
                                } else if field_name.to_lowercase() == "uri" {
                                    let mut key_url = String::new();
                                    if !field_value.starts_with("http")
                                        || field_value.starts_with("\"http")
                                    {
                                        key_url.push_str(self.base_path_url.as_str());
                                        key_url.push(path::MAIN_SEPARATOR);
                                    }
                                    if field_value.starts_with("\"") {
                                        let key_uri_original =
                                            field_value.get(1..field_value.len() - 1).unwrap();
                                        key_url.push_str(key_uri_original);
                                        redirected_line.push_str(
                                            line.replace(
                                                key_uri_original,
                                                extract_resource_name(key_url.as_str()),
                                            )
                                            .as_str(),
                                        );
                                    } else {
                                        key_url.push_str(field_value);
                                    }
                                    self.key_url = key_url;
                                } else {
                                    if self.encrypted_method.len() == 0 {
                                        return Err(Error::new(
                                            ErrorKind::Unsupported,
                                            "unknown encryption method.",
                                        ));
                                    }
                                }
                            }
                        }
                    }
                }
            } else if found && !line.is_empty() {
                found = false;
                let mut url = String::new();
                if !line.starts_with("http") {
                    if line.starts_with("/") {
                        url.push_str(self.base_url.as_str());
                    } else {
                        url.push_str(self.base_path_url.as_str());
                        url.push(path::MAIN_SEPARATOR);
                    }
                }
                url.push_str(line.as_str());

                self.chunk_list.push(String::from(url.as_str()));

                redirected_line.push_str(extract_resource_name(url.as_str()));
            } else if line.starts_with("#EXTINF:") {
                found = true;
                redirected_line.push_str(line.as_str());
            } else {
                redirected_line.push_str(line.as_str());
            }
            redirected_m3u8_index.push_str(redirected_line.as_str());
            redirected_m3u8_index.push('\n');
        }
        //rewrite index.m3u8
        fs::write(self.index_redirected_path.as_str(), redirected_m3u8_index);
        println!("m3u8 play list is parsed.");
        Ok(())
    }
}

pub struct M3u8ChunkListener {
    progress_bar: ProgressBar,
    pub destination: String,
    download_file_map: HashMap<String, String>,
}

impl M3u8ChunkListener {
    pub fn new(history: &History) -> Self {
        let mut download_map = HashMap::new();
        for chunk_file in history.iter() {
            let filename = util::extract_resource_name(chunk_file.as_str());
            download_map.insert(filename.to_string(), chunk_file.to_string());
        }

        Self {
            destination: String::new(),
            progress_bar: ProgressBar::new(),
            download_file_map: download_map,
        }
    }
}

impl ProgressMonitor for M3u8ChunkListener {
    fn on_start(&mut self, url: &str, source_name: &str, destination: &str) {
        println!("{} -> {}", source_name, destination);
        self.progress_bar.set_title(source_name);
        self.destination = String::from(destination);
    }

    fn is_downloaded(&self, source_name: &str) -> bool {
        self.download_file_map.contains_key(source_name)
    }

    fn on_downloaded(&mut self, source_name: &str, url: &str) -> () {
        self.download_file_map
            .insert(source_name.to_string(), url.to_string());
    }

    fn on_update(&mut self, downloaded: u64, total: u64) {
        self.progress_bar
            .set_progress((downloaded as f64 / total as f64 * 100.0) as f32);
        let extra = format!("({}/{})", downloaded, total);
        self.progress_bar.display_with_extra(extra.as_str());
    }

    fn on_end(&mut self, source_name: &str, downloaded: u64, total: u64) -> std::io::Result<()> {
        println!("");
        println!("{} downloaded.", source_name);
        Ok(())
    }
}
